/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.support;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.springframework.aop.Advisor;
import org.springframework.aop.AopInvocationException;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.IntroductionAwareMethodMatcher;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetClassAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodIntrospector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
 * Utility methods for AOP support code.
 *
 * <p>Mainly for internal use within Spring's AOP support.
 *
 * <p>See {@link org.springframework.aop.framework.AopProxyUtils} for a
 * collection of framework-specific AOP utility methods which depend
 * on internals of Spring's AOP framework implementation.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @see org.springframework.aop.framework.AopProxyUtils
 */
public abstract class AopUtils {

	/**
	 * Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
	 * <p>This method additionally checks if the given object is an instance
	 * of {@link SpringProxy}.
	 * @param object the object to check
	 * @see #isJdkDynamicProxy
	 * @see #isCglibProxy
	 */
	public static boolean isAopProxy(@Nullable Object object) {
		return (object instanceof SpringProxy &&
				(Proxy.isProxyClass(object.getClass()) || ClassUtils.isCglibProxyClass(object.getClass())));
	}

	/**
	 * Check whether the given object is a JDK dynamic proxy.
	 * <p>This method goes beyond the implementation of
	 * {@link Proxy#isProxyClass(Class)} by additionally checking if the
	 * given object is an instance of {@link SpringProxy}.
	 * @param object the object to check
	 * @see java.lang.reflect.Proxy#isProxyClass
	 */
	public static boolean isJdkDynamicProxy(@Nullable Object object) {
		return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass()));
	}

	/**
	 * Check whether the given object is a CGLIB proxy.
	 * <p>This method goes beyond the implementation of
	 * {@link ClassUtils#isCglibProxy(Object)} by additionally checking if
	 * the given object is an instance of {@link SpringProxy}.
	 * @param object the object to check
	 * @see ClassUtils#isCglibProxy(Object)
	 */
	public static boolean isCglibProxy(@Nullable Object object) {
		return (object instanceof SpringProxy && ClassUtils.isCglibProxy(object));
	}

	/**
	 * Determine the target class of the given bean instance which might be an AOP proxy.
	 * <p>Returns the target class for an AOP proxy or the plain class otherwise.
	 * @param candidate the instance to check (might be an AOP proxy)
	 * @return the target class (or the plain class of the given object as fallback;
	 * never {@code null})
	 * @see org.springframework.aop.TargetClassAware#getTargetClass()
	 * @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass(Object)
	 */
	public static Class<?> getTargetClass(Object candidate) {
		Assert.notNull(candidate, "Candidate object must not be null");
		Class<?> result = null;
		if (candidate instanceof TargetClassAware) {
			result = ((TargetClassAware) candidate).getTargetClass();
		}
		if (result == null) {
			result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
		}
		return result;
	}

	/**
	 * Select an invocable method on the target type: either the given method itself
	 * if actually exposed on the target type, or otherwise a corresponding method
	 * on one of the target type's interfaces or on the target type itself.
	 * @param method the method to check
	 * @param targetType the target type to search methods on (typically an AOP proxy)
	 * @return a corresponding invocable method on the target type
	 * @throws IllegalStateException if the given method is not invocable on the given
	 * target type (typically due to a proxy mismatch)
	 * @since 4.3
	 * @see MethodIntrospector#selectInvocableMethod(Method, Class)
	 */
	public static Method selectInvocableMethod(Method method, @Nullable Class<?> targetType) {
		if (targetType == null) {
			return method;
		}
		Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
		if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) &&
				SpringProxy.class.isAssignableFrom(targetType)) {
			throw new IllegalStateException(String.format(
					"Need to invoke method '%s' found on proxy for target class '%s' but cannot " +
					"be delegated to target bean. Switch its visibility to package or protected.",
					method.getName(), method.getDeclaringClass().getSimpleName()));
		}
		return methodToUse;
	}

	/**
	 * Determine whether the given method is an "equals" method.
	 * @see java.lang.Object#equals
	 */
	public static boolean isEqualsMethod(@Nullable Method method) {
		return ReflectionUtils.isEqualsMethod(method);
	}

	/**
	 * Determine whether the given method is a "hashCode" method.
	 * @see java.lang.Object#hashCode
	 */
	public static boolean isHashCodeMethod(@Nullable Method method) {
		return ReflectionUtils.isHashCodeMethod(method);
	}

	/**
	 * Determine whether the given method is a "toString" method.
	 * @see java.lang.Object#toString()
	 */
	public static boolean isToStringMethod(@Nullable Method method) {
		return ReflectionUtils.isToStringMethod(method);
	}

	/**
	 * Determine whether the given method is a "finalize" method.
	 * @see java.lang.Object#finalize()
	 */
	public static boolean isFinalizeMethod(@Nullable Method method) {
		return (method != null && method.getName().equals("finalize") &&
				method.getParameterCount() == 0);
	}

	/**
	 * Given a method, which may come from an interface, and a target class used
	 * in the current AOP invocation, find the corresponding target method if there
	 * is one. E.g. the method may be {@code IFoo.bar()} and the target class
	 * may be {@code DefaultFoo}. In this case, the method may be
	 * {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
	 * <p><b>NOTE:</b> In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod},
	 * this method resolves Java 5 bridge methods in order to retrieve attributes
	 * from the <i>original</i> method definition.
	 * @param method the method to be invoked, which may come from an interface
	 * @param targetClass the target class for the current invocation.
	 * May be {@code null} or may not even implement the method.
	 * @return the specific target method, or the original method if the
	 * {@code targetClass} doesn't implement it or is {@code null}
	 * @see org.springframework.util.ClassUtils#getMostSpecificMethod
	 */
	public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
		Class<?> specificTargetClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
		Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
		// If we are dealing with method with generic parameters, find the original method.
		return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
	}

	/**
	 * Can the given pointcut apply at all on the given class?
	 * <p>This is an important test as it can be used to optimize
	 * out a pointcut for a class.
	 * @param pc the static or dynamic pointcut to check
	 * @param targetClass the class to test
	 * @return whether the pointcut can apply on any method
	 */
	public static boolean canApply(Pointcut pc, Class<?> targetClass) {
		return canApply(pc, targetClass, false);
	}

	/**
	 * Can the given pointcut apply at all on the given class?
	 * 给定的切入点是否完全适用于给定的类？
	 * <p>This is an important test as it can be used to optimize
	 * out a pointcut for a class.
	 * 这是一个重要的测试，因为它可用于优化类的切入点。
	 * @param pc the static or dynamic pointcut to check
	 * @param targetClass the class to test
	 *                    要测试的类
	 * @param hasIntroductions whether or not the advisor chain
	 * for this bean includes any introductions
	 *                         此 bean 的 advisor链 是否包含任何 introductions
	 * @return whether the pointcut can apply on any method
	 *         切入点是否可以应用于任何方法
	 *
	 * 这个函数大体脉络，首先获取对应类的所有接口并连同类本身一起遍历，遍历过程中又对类中的方法再次遍历，
	 * 一旦匹配成功便认为这个类适用于当前增强器。
	 */
	public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		Assert.notNull(pc, "Pointcut must not be null");
		// 进行切点表达式的匹配最重要的就是 ClassFilter 和 MethodMatcher 这两个方法的实现。
		// MethodMatcher 中有两个 matches 方法。一个参数是只有Method对象和targetclass，另一个参数有
		// Method对象 和 targetClass对象 还有一个 Method 的方法参数 他们两个的区别是：
		// 两个参数的 matches 是用于静态的方法匹配 三个参数的 matches 是在运行期动态的进行方法匹配的
		// 先进行 ClassFilter 的 matches 方法校验
		// 首先这个类要在所匹配的规则下。类级别的匹配
		if (!pc.getClassFilter().matches(targetClass)) { //Pointcut 是 AspectJExpressionPointcut 类型
			return false;
		}
        // 再进行 MethodMatcher 方法级别的校验。
		// 此时的 pc 表示 TransactionAttributeSourcePointcut，pc.getMethodMatcher()返回的正是自身（this）
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
			// No need to iterate the methods if we're matching any method anyway...
			// 如果我们匹配任何方法，则无需迭代方法......
			return true;
		}

		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; //引介
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}
		// 得到这个 bean 的所有的接口
		Set<Class<?>> classes = new LinkedHashSet<>();
		if (!Proxy.isProxyClass(targetClass)) {
			classes.add(ClassUtils.getUserClass(targetClass)); // 并连同类本身
		}

		// 获取 targetClass 的所有 classes 包括父类
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
		for (Class<?> clazz : classes) { // 遍历接口集合
			// 获取当前类的所有方法。得到实现的接口类的所有方法（包括父类方法） 被代理的 Bean 里所有的方法，包括父类Object的方法。
			// 如：TestBean类中有3个方法：test()、getTestStr()、setTestStr()以及Object的所有方法
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			// 只要有一个方法能匹配到就返回 true
			// 这里就会有一个问题：因为在一个目标中可能会有多个方法存在，有的方法是满足这个切点的匹配规则的
			// 但是也可能有一些方法是不匹配切点规则的，这里检测的是只有一个 Method 满足切点规则就返回 true 了
			// 所以在运行时进行方法拦截的时候还会有一次运行时的方法切点规则匹配
			for (Method method : methods) { // 类中的方法遍历
				// 如果 IntroductionAwareMethodMatcher 或者 methodMatcher 和目标 bean 的方法匹配
				if (introductionAwareMethodMatcher != null ?
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						// 一旦匹配成功便认为这个类适用于当前增强器
						// 会使用 TransactionAttributeSourcePointcut 类的 matches 方法做匹配
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Can the given advisor apply at all on the given class?
	 * This is an important test as it can be used to optimize
	 * out a advisor for a class.
	 * @param advisor the advisor to check
	 * @param targetClass class we're testing
	 * @return whether the pointcut can apply on any method
	 */
	public static boolean canApply(Advisor advisor, Class<?> targetClass) {
		return canApply(advisor, targetClass, false);
	}

	/**
	 * Can the given advisor apply at all on the given class?
	 * 给定的增强可以完全适用于给定的类吗？
	 * <p>This is an important test as it can be used to optimize out a advisor for a class.
	 * This version also takes into account introductions (for IntroductionAwareMethodMatchers).
	 * @param advisor the advisor to check
	 * @param targetClass class we're testing
	 * @param hasIntroductions whether or not the advisor chain for this bean includes
	 * any introductions
	 * @return whether the pointcut can apply on any method
	 *         切入点是否可以应用于任何方法
	 */
	public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
		//如果是 IntroductionAdvisor 的话，则调用 IntroductionAdvisor 类型的实例进行类的过滤
		//这里是直接调用的 ClassFilter 的 matches 方法。
		if (advisor instanceof IntroductionAdvisor) {
			return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
		}
		// 通常我们的 Advisor 都是 PointcutAdvisor 类型。
		// 所有的增强都由 Advisor 的实现类 InstantiationModelAwarePointcutAdvisorImpl 统一封装的。《Spring源码深度解析》P180
		// InstantiationModelAwarePointcutAdvisorImpl 是 InstantiationModelAwarePointcutAdvisor 的实现类
		// InstantiationModelAwarePointcutAdvisor extends PointcutAdvisor
		// PointcutAdvisor extends Advisor
		// 事务在注解中注册的 BeanFactoryTransactionAttributeSourceAdvisor 也实现了 PointcutAdvisor
		else if (advisor instanceof PointcutAdvisor) {
			PointcutAdvisor pca = (PointcutAdvisor) advisor;
			// 这里从 Advisor 中获取的是 Pointcut 的实现类，这里是 AspectJExpressionPointcut
			// 参见：InstantiationModelAwarePointcutAdvisorImpl 中 this.pointcut = this.declaredPointcut;
			// this.declaredPointcut 要构造器的形参是 AspectJExpressionPointcut 类型
			return canApply(pca.getPointcut(), targetClass, hasIntroductions);
		}
		else {
			// It doesn't have a pointcut so we assume it applies.
			// 它没有切入点，所以我们假设它适用。
			return true;
		}
	}

	/**
	 * Determine the sublist of the {@code candidateAdvisors} list
	 * that is applicable to the given class.
	 * 确定适用于给定类的 {@codeCandidateAdvisors} 列表的子列表。
	 * @param candidateAdvisors the Advisors to evaluate 要评估的增强器
	 * @param clazz the target class 目标类
	 * @return sublist of Advisors that can apply to an object of the given class
	 * (may be the incoming List as-is)
	 * 可以应用于给定类的对象的增强子列表（可能是传入的列表）
	 */
	// 挑取出适合的增强器，也就是满足我们配置的通配符的增强器
	// 做了3件事，找出能够应用于该 bean 的 Advisor：
	//  1.如果候选Advisor为空，直接返回
	//  2.遍历候选Advisor，如果 Advisor 是 IntroductionAdvisor 的实例并且是该目标Bean要用的（canApply返回为true,这个方法后面介绍），加入到返回列表中。
	//  3.遍历候选Advisor，如果不是 IntroductionAdvisor 的实例，但是该目标Bean要用的（canApply返回为true,这个方法后面介绍），加入到返回列表中。
	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		// 如果传入的 Advisor集合 为空的话，直接返回这个空集合。
		// 这里没有判断 candidateAdvisors 不为 null 的情况 因为在获取 Advisor 的地方是先创建一个空的集合，
		// 再进行添加 Advisor 的动作，不过还是加一下不为 null 的判断更好一点。
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}
		List<Advisor> eligibleAdvisors = new ArrayList<>();

		// 首先处理 IntroductionAdvisor Advisor（引介增强）,循环所有的Advisor
		for (Advisor candidate : candidateAdvisors) {
			//如果 Advisor 是 IntroductionAdvisor 引介增强 可以为目标类 通过AOP的方式添加一些接口实现
			//引介增强是一种比较我们接触的比较少的增强。IntroductionAdvisor 继承了 Advisor接口。
			//IntroductionAdvisor 只能应用于类级别的拦截，只能使用 Introduction型 的Advice。
			if (candidate instanceof IntroductionAdvisor // 匹配 IntroductionAdvisor 增强器
					&& canApply(candidate, clazz)) {     // 判断，是否可以匹配
				eligibleAdvisors.add(candidate);
			}
		}

		// 然后，处理非 IntroductionAdvisor 增强器
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		// 在这个方法的最上面 refresh() -> finishBeanFactoryInitialization(beanFactory); -> beanFactory.preInstantiateSingletons(); 中 for (String beanName : beanNames)
		// 也就是说每一个 bean 都会进来比较一翻是否与这个集中的增强方法匹配。拿MyAspect类和AspectJTest类测试，candidateAdvisors里面有4个增强器方法。AspectJTest，MyAspect
		for (Advisor candidate : candidateAdvisors) {
			// 引用增强已经处理，如果是 IntroductionAdvisor 类型的话，则直接跳过
			if (candidate instanceof IntroductionAdvisor) {
				// already processed 已经处理
				continue;
			}
			// 对于普通的 bean 的处理，判断此 Advisor（增强器） 是否适用于 target (被代理的目标对象）
			// findAdvisorsThatCanApply()函数 的主要功能是寻找所有增强器中适用于当前class 的增强器。
			// 引介增强与普通的增强处理是不一样的，所以分开处理。而对于真正的匹配在 canApply() 中实现。
			if (canApply(candidate, clazz, hasIntroductions)) { // 找出能够应用于该 bean 的 Advisor
				eligibleAdvisors.add(candidate); // 加入到返回列表
			}
		}

		return eligibleAdvisors;
	}

	/**
	 * Invoke the given target via reflection, as part of an AOP method invocation.
	 * @param target the target object
	 * @param method the method to invoke
	 * @param args the arguments for the method
	 * @return the invocation result, if any
	 * @throws Throwable if thrown by the target method
	 * @throws org.springframework.aop.AopInvocationException in case of a reflection error
	 */
	@Nullable
	public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
			throws Throwable {

		// Use reflection to invoke the method.
		try {
			ReflectionUtils.makeAccessible(method);
			return method.invoke(target, args);
		}
		catch (InvocationTargetException ex) {
			// Invoked method threw a checked exception.
			// We must rethrow it. The client won't see the interceptor.
			throw ex.getTargetException();
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
					method + "] on target [" + target + "]", ex);
		}
		catch (IllegalAccessException ex) {
			throw new AopInvocationException("Could not access method [" + method + "]", ex);
		}
	}

}
